import time
import os
import threading
# ----------------------------------------
import cv2
# --------------------------------------------------


class Cap:
    """Wrapper of OpenCV VideoCapture object for Humans

    # Features: --------------------------------------------------
        1. Callback-based mechanism for per frame capture
        2. Timeout-based mechanism for maximum continuous capture time
        3. Sleeping-based mechanism for simulate capture interval when reading video file


    # Examples: --------------------------------------------------
        # 1. Instantiation
            cap = Cap(cap_id, uri)

        # 2. Starting capture
            def callback(frame_info, *args, **kwargs):
                cap_id, offset, frame = frame_info
                ...

            cap.start(callback, args=(...), kwargs={...})

        # 3. Stopping capture
            cap.stop()
    """

    def __init__(self, cap_id, uri, rate=7):
        self.id = cap_id
        self.uri = uri
        self._cap = cv2.VideoCapture(uri)
        self._is_opened()                # If not opened, raise IOError
        # --------------------------------------------------
        self._status = False             # The cap is idle default
        self._lock = threading.RLock()
        # --------------------------------------------------
        self._isfile = os.path.isfile(uri)
        try:
            self._interval = 1 / rate    # Interval between frames reading
        except ZeroDivisionError:
            self._interval = 0
        # --------------------------------------------------
        self.local = threading.local()

    def start(self, callback, args=(), kwargs=None, timeout=0):
        with self._lock:                 # Can start capture when previous worker is finished only
            self._status = True
            self._timeout = timeout      # Maximum continuous capture time save from forgetting stopping
            # --------------------------------------------------
            threading.Thread(daemon=True, target=self._worker,
                             args=(callback,)+args, kwargs=kwargs).start()

    def stop(self):
        self._status = False
        # --------------------------------------------------
        if self._isfile:
            self._reset_file()

    def _is_opened(self):
        if not self._cap.isOpened():
            raise IOError("<Cap: %s> is unavailable." % self.id)

    def _reset_file(self):
        with self._lock:                 # Can reset cap if previous worker unfinished
            self._cap.release()
            self._cap.open(self.uri)

    def _worker(self, callback, *args, **kwargs):
        with self._lock:
            offset = 0
            start_time = time.time()
            # --------------------------------------------------
            while self._status:
                if self._timeout and (time.time()-start_time >= self._timeout):
                    self.stop()
                    break
                # ----------------------------------------
                ret, frame = self._cap.read()
                if not ret:              # ret is boolean value used to represent success or failure
                    self._isfile and time.sleep(self._interval)
                    continue
                # ----------------------------------------
                frame_info = self, offset, frame
                callback(frame_info, *args, **kwargs)
                offset += 1
                self._isfile and time.sleep(self._interval)
            # --------------------------------------------------
            callback((self, -1, None), *args, **kwargs)  # End-mark using offset -1 with no frame

    def __del__(self):
        self._status = False
        self._cap.release()
        del self._cap


if __name__ == '__main__':
    cap1 = Cap(1, 0)
    cap2 = Cap(2, 0)

    def print_frame_info(frame_info, *args, **kwargs):
        cap, offset, frame = frame_info
        # --------------------------------------------------
        if offset == 0:  # ----------------------------------------
            cap.local.frames = []
        elif offset == -1:  # ----------------------------------------
            print('%s 共 %s 帧' % (cap.id, len(cap.local.frames)))
        else:  # ----------------------------------------
            print(cap.id, offset)
            cap.local.frames.append(frame)

    cap1.start(print_frame_info, timeout=5)
    cap2.start(print_frame_info, timeout=5)
    time.sleep(6)
    cap1.start(print_frame_info, timeout=5)
    time.sleep(6)
    time.sleep(1)
